В этом задании вам предстоит взять открытый датасет с SMS-сообщениями, размеченными на спам ("spam") и не спам ("ham"), построить на нем классификатор текстов на эти два класса, оценить его качество с помощью кросс-валидации, протестировать его работу на отдельных примерах, и посмотреть, что будет происходить с качеством, если менять параметры вашей модели.
In [1]:
from __future__ import division, print_function
import numpy as np
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import MultinomialNB
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import Pipeline
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
1. Загрузите датасет. Описание датасета можно посмотреть здесь:
In [2]:
# reading dataset
with open('smsspamcollection/SMSSpamCollection.txt', 'r') as f:
sms = f.read().splitlines()
In [3]:
sms[0]
sms[11]
Out[3]:
Out[3]:
2. Подготовьте для дальнейшей работы два списка: список текстов в порядке их следования в датасете и список соответствующих им меток классов. В качестве метки класса используйте 1 для спама и 0 для "не спама".
In [4]:
# sms_label - labels('ham', 'spam')
# sms_text - sms texts
sms_label, sms_text = zip(*[line.split('\t') for line in sms])
# sms_label_bi - 0 for ham, 1 for spam
sms_label_bi = np.array([0 if t=='ham' else 1 for t in sms_label])
In [5]:
sms_label[0:5]
sms_label_bi[0:5]
sms_text[0:5]
Out[5]:
Out[5]:
Out[5]:
3. Используя sklearn.feature_extraction.text.CountVectorizer со стандартными настройками, получите из списка текстов матрицу признаков X.
In [6]:
# CountVectorizer for sms_text
count_vect = CountVectorizer()
X_counts = count_vect.fit_transform(sms_text)
X_counts.shape
Out[6]:
4. Оцените качество классификации текстов с помощью LogisticRegression() с параметрами по умолчанию, используя sklearn.cross_validation.cross_val_score и посчитав среднее арифметическое качества на отдельных fold'ах. Установите random_state=2. Параметр cv задайте равным 10. В качестве метрики качества используйте f1-меру. Получившееся качество - один из ответов, которые потребуются при сдаче задания. Ответ округлить до 1 знака после запятой.
In [7]:
pipeline = Pipeline([('count_vect', CountVectorizer()), ('log_reg', LogisticRegression())])
In [8]:
# cross_val_score for f1 metric for log_reg
score = cross_val_score(pipeline, sms_text, sms_label_bi, scoring='f1', cv=10).mean()
print('Cross_val_score f1: %.4f' % score)
In [9]:
with open('ans1.txt', 'w') as f:
ans1 = str(round(score, 1))
f.write(ans1)
5. А теперь обучите классификатор на всей выборке и спрогнозируйте с его помощью класс для следующих сообщений:
"FreeMsg: Txt: CALL to No: 86888 & claim your reward of 3 hours talk time to use from your phone now! Subscribe6GB"
"FreeMsg: Txt: claim your reward of 3 hours talk time"
"Have you visited the last lecture on physics?"
"Have you visited the last lecture on physics? Just buy this book and you will have all materials! Only 99$"
"Only 99$"
In [10]:
sms_test = ["FreeMsg: Txt: CALL to No: 86888 & claim your reward of 3 hours talk time to use from your phone now! Subscribe6GB",
"FreeMsg: Txt: claim your reward of 3 hours talk time",
"Have you visited the last lecture on physics?",
"Have you visited the last lecture on physics? Just buy this book and you will have all materials! Only 99$",
"Only 99$"]
In [11]:
# fitting full dataset
pipeline.fit(sms_text, sms_label_bi)
Out[11]:
In [12]:
# predicting labels for sms_test
sms_test_pr = pipeline.predict(sms_test)
sms_test_pr
Out[12]:
In [13]:
with open('ans2.txt', 'w') as f:
ans2 = ' '.join(map(str, sms_test_pr))
f.write(ans2)
6. Задайте в CountVectorizer параметр ngram_range=(2,2), затем ngram_range=(3,3), затем ngram_range=(1,3). Во всех трех случаях измерьте получившееся в кросс-валидации значение f1-меры, округлите до второго знака после точки, и выпишете результаты через пробел в том же порядке. В данном эксперименте мы пробовали добавлять в признаки n-граммы для разных диапазонов n - только биграммы, только триграммы, и, наконец, все вместе - униграммы, биграммы и триграммы. Обратите внимание, что статистики по биграммам и триграммам намного меньше, поэтому классификатор только на них работает хуже. В то же время это не ухудшает результат сколько-нибудь существенно, если добавлять их вместе с униграммами, т.к. за счет регуляризации линейный классификатор не склонен сильно переобучаться на этих признаках.
In [14]:
# testing log_reg the model in different ngram ranges
ngram_range = [(2,2), (3,3), (1,3)]
scores = []
for ngram in ngram_range:
pipeline = Pipeline([('count_vect', CountVectorizer(ngram_range=ngram)), ('log_reg', LogisticRegression())])
score = cross_val_score(pipeline, sms_text, sms_label_bi, scoring='f1', cv=10).mean()
scores.append(score)
print('Ngram_range: ', ngram)
print('Cross-val-score f1: %.4f\n' % score)
In [15]:
with open('ans3.txt', 'w') as f:
ans3 = ' '.join(map(str, ['%.2f' % sc for sc in scores]))
f.write(ans3)
7. Повторите аналогичный п.6 эксперимент, используя вместо логистической регрессии MultinomialNB(). Обратите внимание, насколько сильнее (по сравнению с линейным классификатором) наивный Байес страдает от нехватки статистики по биграммам и триграммам.
In [16]:
# testing mult_nb the model in different ngram ranges
scores = []
for ngram in ngram_range:
pipeline = Pipeline([('count_vect', CountVectorizer(ngram_range=ngram)), ('mn_nb', MultinomialNB())])
score = cross_val_score(pipeline, sms_text, sms_label_bi, scoring='f1', cv=10).mean()
scores.append(score)
print('Ngram_range: ', ngram)
print('Cross-val-score f1: %.4f\n' % score)
In [17]:
with open('ans4.txt', 'w') as f:
ans4 = ' '.join(map(str, ['%.2f' % sc for sc in scores]))
f.write(ans4)
In [18]:
# testing mult_nb the model in different ngram ranges
# !!!another results from above due to pipeline (reason unknown)!!!
scores = []
for ngram in ngram_range:
X_counts = CountVectorizer(ngram_range=ngram).fit_transform(sms_text)
mn_nb = MultinomialNB()
score = cross_val_score(mn_nb, X_counts, sms_label_bi, scoring='f1', cv=10).mean()
scores.append(score)
print('Ngram_range: ', ngram)
print('Cross-val-score f1: %.4f\n' % score)
In [19]:
with open('ans4.txt', 'w') as f:
ans4 = ' '.join(map(str, ['%.2f' % sc for sc in scores]))
f.write(ans4)
8. Попробуйте использовать в логистической регрессии в качестве признаков Tf*idf из TfidfVectorizer на униграммах. Повысилось или понизилось качество на кросс-валидации по сравнению с CountVectorizer на униграммах? (напишите в файле с ответом 1, если повысилось, -1, если понизилось, и 0, если изменилось не более чем на 0.01). Обратите внимание, что результат перехода к tf*idf не всегда будет таким - если вы наблюдаете какое-то явление на одном датасете, не надо сразу же его обобщать на любые данные.
In [20]:
# cross_val_score for f1 metric for log_reg with tf_idf
pipeline = Pipeline([('tf_idf', TfidfVectorizer()),
('log_reg', LogisticRegression())])
score = cross_val_score(pipeline, sms_text, sms_label_bi, scoring='f1', cv=10).mean()
print('Cross-val-score f1: %.4f\n' % score)
In [21]:
with open('ans5.txt', 'w') as f:
ans5 = str(-1)
f.write(ans5)